import "../../lib/ramda.js"
import "../../lib/jquery.js"
import game from "./game.js"
import direct2d from "../../lib/direct2d.js"
import utils from "../../lib/utils.js"
import aiport from "./ai-port.js"

const Consts = {
    BOARD_LEN_RATE:0.98,                        // 棋盘画布边长相对于父元素的比率
    BOARD_LINE_COLOR:"#101010",                 // 交叉线颜色
    BOARD_STAR_COLOR:"#202020",                 // 星位点颜色
    BOARD_STAR_SIZE_RATE:0.2,                   // 星位点大小相对格子间距的比率
    BOARD_LABEL_FONT:"Courier New",             // 棋盘坐标字体
    BOARD_LABEL_COLOR:"#000000",                // 棋盘坐标颜色
    BOARD_SELECT_COLOR:"#FFFFFF",               // 棋盘选择点颜色
    PIECE_BLACK_COLOR:"#151515",                // 黑棋颜色
    PIECE_WHITE_COLOR:"#EEEEEE",                // 白棋颜色
    PIECE_STEP_FONT:"Arial",                    // 棋子步数字体
    PIECE_STEP_COLOR:["#111111","#EEEEEE"],     // 棋子步数颜色
    PIECE_SELECT_COLOR:"#EE5050",               // 打点时的步数颜色
    FORBIDDEN_MOVE_COLOR:"#EE2020",             // 禁手标志颜色
    PC_PAGEWIDTH_RATE:0.54,                     // PC端页面宽度和高度的比率
}

const aiwrap = {
    send: R.always(undefined),
    _to_port_pflag(pflag){
        if (pflag == game.P_BLACK){return aiport.P_BLACK}
        if (pflag == game.P_WHITE){return aiport.P_WHITE}
        return aiport.P_EMPTY
    },
    move(pflag, can_be_swap, second, on_result){
        const P = aiwrap._to_port_pflag(pflag)
        const S = (can_be_swap ? "y" : "n")
        const T = Math.min(aiport.STEP_SECOND_MAX, second)
        aiwrap.send(aiport.push(aiport.encode("ai.move", [P, S, T]))
            .onreply(on_result))
        return aiwrap
    },
    set_board(board){
        const port_board = aiport.Board()
        game.board_for_pieces(board, (pflag, i)=>{
            const [x,y] = [game.to_x(i), game.to_y(i)]
            if (pflag == game.P_BLACK){
                port_board.set(x, y, aiport.P_BLACK)
            }else if (pflag == game.P_WHITE){
                port_board.set(x, y, aiport.P_WHITE)
            }
        })
        aiwrap.send(aiport.encode("ai.set_board", port_board.pack()))
        return aiwrap
    },
    set_rule_switch(name, on){
        aiwrap.send(aiport.encode("ai.set_rule", [name, (on ? "on" : "off")]))
        return aiwrap
    },
    ask_select_a(pflag, locs, on_result){
        const P = aiwrap._to_port_pflag(pflag)
        const A = locs.map((i)=>(aiport.Location(i).pack()))
        aiwrap.send(aiport.push(aiport.encode("ai.select-a", [P, ...A])).onreply(on_result))
        return aiwrap
    },
    ask_selects(pflag, sel_nums, has_sels, on_result){
        const P = aiwrap._to_port_pflag(pflag)
        const N = []
        for (let i = 0; i < sel_nums; i++){N[i] = "?"}
        for (let i in has_sels){
            N[i] = aiport.Location(has_sels[i]).pack()
        }
        aiwrap.send(aiport.push(aiport.encode("ai.selects?", [P, ...N])).onreply(on_result))
        return aiwrap
    },
    ask_swap(on_result){
        aiwrap.send(aiport.push(aiport.encode("ai.swap?")).onreply(on_result))
        return aiwrap
    },
    set_args(...v){
        aiwrap.send(aiport.encode("ai.set_rule", ["!args", ...v]))
    },
    reset(){
        aiwrap.send(aiport.encode("ai.reset"))
        return aiwrap
    },
    end(){
        aiwrap.send(aiport.encode("ai.end"))
        return aiwrap
    },
}
const Data = {
    select: undefined,                  // 玩家在棋盘中选择的点
    forbidden_moves: undefined,
    winner_pflag: undefined,
    lose_note: undefined,
    has_over: undefined,
    maxtime_total: undefined,
    maxtime_step: undefined,
    select_nums: undefined,
    swap_nums: undefined,
    rule: undefined,
    // for ai
    use_ai: [false, false],
    ai_in_think: false,
    ai_last_args: undefined,
    ai_say: undefined,
    kvs: undefined,
    init(kvs){
        Data.kvs = kvs
    },
    reset(){
        Data.select = -1
        Data.forbidden_moves = []
        Data.winner_pflag = game.P_EMPTY
        Data.lose_note = ""
        Data.has_over = false
        Data.maxtime_total = Data.kvs.number("maxtime-total")
        Data.maxtime_step = Data.kvs.number("maxtime-step")
        Data.select_nums = -1
        Data.swap_nums = 0
        Data.rule = Data.kvs.text("rule")
        Data.ai_in_think = false
        Data.ai_last_args = [
            Data.kvs.text("ai.uct-max-time"),
            Data.kvs.text("ai.vc-max-depth"),
            Data.kvs.text("ai.search-depth"),
        ]
        Data.ai_say = ""
    },
    pflag_to_player(flag){
        return (flag == game.P_BLACK) ? 0 : 1
    },
    format_lines(lines){
        return `(横：${game.SHAPE_NAME[lines[0]]}) (竖：${game.SHAPE_NAME[lines[1]]}) (左斜：${game.SHAPE_NAME[lines[2]]}) (右斜：${game.SHAPE_NAME[lines[3]]})`
    },
    shape_note(lines){
        const max = Math.max(lines[0], lines[1], lines[2], lines[3])
        let count_l3 = 0
        let count_s4 = 0
        for (let val of lines){
            if (val == game.L3){count_l3++}
            else if (val == game.S4){count_s4++}
        }
        if ((count_s4 > 0) && (count_l3 > 0)){return "冲四活三"}
        if (count_l3 > 1){return "双活三"}
        return game.SHAPE_NAME[max]
    },
}

const Ux = {
    _boxs:{},
    _box_last:undefined,
    box_cur(){return Ux._box_last},
    box_to(id){
        if (Ux._box_last != undefined){
            Ux._boxs[Ux._box_last].dom.hide()
            Ux._boxs[Ux._box_last].dom_tab.removeClass("label-cur")
        }
        Ux._boxs[id].dom.show()
        Ux._boxs[id].dom_tab.addClass("label-cur")
        Ux._box_last = id
    },
    box_add(dom){
        const id = dom.attr("ux-box")
        const dom_tab = $(`#tabs-to-${id}`)
        Ux._boxs[id] = {dom, dom_tab}
        dom_tab.bind("click", ()=>{Ux.box_to(id)})
    },
    _frameps:{
        "box":{},
        "normal":{},
        "_add":[],
    },
    frame_add(path, ctor){
        if (path instanceof Array){
            if (R.length(path) > 1){
                Ux._frameps["_add"].push({_path:path, ctor})
            }
        } else {
            Ux._frameps["_add"].push({_path:["normal", path], ctor})
        }
        return Ux
    },
    frame_add_done(){
        Ux._frameps["_add"].forEach((obj)=>{
            Ux._frameps[obj._path[0]][obj._path[1]] = obj.ctor()
        })
        Ux._frameps["_add"] = undefined
    },
    frame(){
        const rfs = Ux._frameps
        function frame_box(id){
            if (id != undefined){
                rfs["box"][id]()
            }
        }
        function frame_by(type){
            for (const name in rfs[type]){
                rfs[type][name]()
            }
        }
        frame_by("normal")
        frame_box(Ux.box_cur())
    },
    _state:{},
    _state_watchs:{},
    state:undefined,
    state_watch(name, f){
        if (name in Ux._state_watchs){
            const watchs = Ux._state_watchs[name]
            if (watchs instanceof Array){
                watchs.push(f)
            } else {
                Ux._state_watchs[name] = [f]
            }
        } else {
            Ux._state_watchs[name] = [f]
        }
    },
}

const GameRecord = (()=>{
    const _log = []
    const _backs = []
    let _update_time = utils.clock.now_ms()
    return {
        clear(){
            _log.splice(0)
            _backs.splice(0)
            _update_time = utils.clock.now_ms()
            return this
        },
        update_time:()=>_update_time,
        step_nums(){return _log.length},
        at(i){return _log[i]},
        last(){return _log[_log.length-1]},
        move(loc, pflag, note=""){
            _log.push({loc, pflag, note})
            _update_time = utils.clock.now_ms()
            return this
        },
        back(){
            if (_log.length > 0){
                const last = _log.pop()
                _backs.push(last)
                _update_time = utils.clock.now_ms()
                return true
            }
            return false
        },
        unback(){
            if (_backs.length > 0){
                const last = _backs.pop()
                this.move(last.loc, last.pflag, last.note)
                _update_time = utils.clock.now_ms()
                return true
            }
            return false
        },
        foreach(f){
            _log.forEach((m,i)=>{f(1+i, m)})
            return this
        }
    }
})()

const Event = {
    on_cmd_timer_switch(){
        if (Data.has_over){return false}
        if (Data.ai_in_think){return false}
        if (game.PK.timer_has_start()){
            game.PK.timer_stop()
        }else{
            game.PK.timer_start()
        }
        return true
    },
    on_move(loc){
        if (Data.has_over){return}
        if (Data.ai_in_think){return}
        const pflag = game.PK.next_pflag()
        const result = game.Rules[Data.rule].for_point(game.PK.board(), loc, pflag)
        // 特殊情况：打点
        if (Data.select_nums > 0){
            if (result != game.RuleBase.NORMAL_MOVE){return}
            game.PK.select_add(loc)
            Data.select_nums--
        // 正常走棋/选择打点
        }else{
            // 黑棋禁手，白胜
            if (result == game.RuleBase.FORBIDDEN_MOVE){
                Data.winner_pflag = game.P_WHITE
                Data.lose_note = "禁手"
                Data.has_over = true
                // 正常胜
            } else if (result == game.RuleBase.WINNER_MOVE){
                Data.winner_pflag = pflag
                Data.lose_note = ""
                Data.has_over = true
            } else {
                // 选择保留的打点必须有效
                if (0 == Data.select_nums){if (! game.PK.select_in(loc)){return}}
                // 无效走法不能进谱
                else if (result == game.RuleBase.INVALID_MOVE){return}
            }
            Data.select_nums = ((0 == Data.select_nums) ? -2 : ((-2 == Data.select_nums) ? -3 : -1))
            game.PK.move(loc)
            GameRecord.move(loc, pflag, Data.shape_note(game.point(game.PK.board(), loc, pflag)))
            // 检测空位 没有则和棋
            if (game.board_sum_piece(game.PK.board(), game.P_EMPTY) < 1){
                Data.winner_pflag = game.P_EMPTY
                Data.has_over = true
            }
        }
        if (! game.PK.timer_has_start()){game.PK.timer_start()}
        Data.swap_nums = ((0 == Data.swap_nums) ? 0 : 3)
        if (Data.has_over){
            game.PK.timer_stop()
        }else if (Data.select_nums < 0){
            // 在走棋后更新禁手点
            if (game.Rules[Data.rule].use_forbidden){
                const forbidden_moves = []
                game.PK.for_pieces((pflag, loc)=>{
                    if (pflag == game.P_EMPTY){
                        const result = game.Rules[Data.rule].for_point(game.PK.board(), loc, game.P_BLACK)
                        if (result == game.RuleBase.FORBIDDEN_MOVE){forbidden_moves.push(loc)}
                    }
                })
                Data.forbidden_moves = forbidden_moves
            }
            // 通知AI更新棋盘
            aiwrap.set_board(game.PK.board())
        }
        // 更新棋盘选择点
        Data.select = loc
    },
    on_prev(){
        if (GameRecord.step_nums() < 1){return}
        if (Data.has_over){return}
        if (Data.ai_in_think){return}
        if (Data.select_nums > 0){return}
        game.PK.unmove(GameRecord.last().loc)
        GameRecord.back()
        aiwrap.set_board(game.PK.board())
        if (GameRecord.step_nums() > 0){
            Data.select = GameRecord.last().loc
        }
    },
    on_next(){
        if (Data.has_over){return}
        if (Data.ai_in_think){return}
        if (Data.select_nums > 0){return}
        if (GameRecord.unback()){
            const back = GameRecord.last()
            game.PK.move(back.loc)
            aiwrap.set_board(game.PK.board())
            Data.select = back.loc
        }
    },
    on_load_first(){
    },
    on_load_last(){
    },
    on_giveup(){
        if (Data.has_over){return}
        if (Data.ai_in_think){return}
        Data.winner_pflag = game.P_swap(game.PK.next_pflag())
        Data.lose_note = "投子"
        Data.has_over = true
    },
    on_swap(){
        if (GameRecord.step_nums() < 1){return}
        if (Data.has_over){return}
        if (Data.ai_in_think){return}
        if ((1 == Data.swap_nums) && (Data.use_ai[0] ^ Data.use_ai[1])){
            Data.use_ai[0] = !(Data.use_ai[0])
            Data.use_ai[1] = !(Data.use_ai[1])
            Data.swap_nums = 2
        }
    },
    on_timer(){
        if (! Data.has_over){
            const time_step = game.PK.timer_step(game.PK.next_pflag())
            if (time_step < 1){
                Data.has_over = true
                Data.winner_pflag = game.P_swap(game.PK.next_pflag())
                Data.lose_note = `超时`
                game.PK.timer_stop()
            }
        }
    },
    on_ai_step(){
        const ai_pflag = game.PK.next_pflag()
        function move(){
            const able_second = game.PK.timer_step(ai_pflag)
            if (able_second < aiport.STEP_SECOND_MIN){return}
            if ((Data.ai_last_args[0] != Data.kvs.text("ai.uct-max-time")) ||
                (Data.ai_last_args[1] != Data.kvs.text("ai.vc-max-depth")) ||
                (Data.ai_last_args[2] != Data.kvs.text("ai.search-depth"))){
                Data.ai_last_args[0] = Data.kvs.text("ai.uct-max-time")
                Data.ai_last_args[1] = Data.kvs.text("ai.vc-max-depth")
                Data.ai_last_args[2] = Data.kvs.text("ai.search-depth")
                aiwrap.set_args(...(Data.ai_last_args))
            }
            aiwrap.move(ai_pflag, game.Rules[Data.rule].can_swap(game.PK.next_step() +1), able_second, (loc_pack)=>{
                if ("pass" == loc_pack){
                    Data.ai_in_think = false
                    return Event.on_giveup()
                }
                const loc = aiport.Location(loc_pack).i()
                game.PK.timer_start()
                Data.ai_say = ""
                Data.ai_in_think = false
                Event.on_move(loc)
            })
        }
        function begin_fuck(){
            // 可交换时先询问AI是否交换(仅当只有只有一方为AI时)
            if ((1 == Data.swap_nums) && (Data.use_ai[0] ^ Data.use_ai[1])){
                aiwrap.ask_swap((p)=>{
                    const ai_select = aiport.Piece(p)
                    const pflag = (ai_select.is_black() ? game.P_BLACK : (ai_select.is_white() ? game.P_WHITE : game.P_EMPTY))
                    // 若不交换继续让AI走棋
                    if ((pflag == ai_pflag)){
                        move()
                    }else{
                        Data.use_ai[0] = !(Data.use_ai[0])
                        Data.use_ai[1] = !(Data.use_ai[1])
                        Data.swap_nums = 2
                        Data.ai_in_think = false
                    }
                })
            // 需要AI打点
            }else if (Data.select_nums > 0){
                function error(){
                    Data.winner_pflag = game.P_swap(ai_pflag)
                    Data.lose_note = "AI打点违反规则"
                    Data.has_over = true
                }
                aiwrap.ask_selects(ai_pflag, (Data.select_nums + game.PK.selects().length), game.PK.selects(), (...loc_packs)=>{
                    for (let port_loc of loc_packs.map((a)=>(aiport.Location(a)))){
                        const loc = port_loc.i()
                        if ((game.board_get(game.PK.board(), loc) != game.P_EMPTY) ||
                            (game.PK.select_in(loc))){
                            console.log(Data.select_nums, game.text_i(loc))
                            return error()
                        }else{
                            game.PK.select_add(loc)
                            Data.select_nums--
                        }
                    }
                    if (Data.select_nums != 0){return error()}
                    Data.ai_in_think = false
                })
            // 需要AI选择打点
            }else if (0 == Data.select_nums){
                function error(){
                    Data.winner_pflag = ai_pflag
                    Data.lose_note = "AI选点违反规则"
                    Data.has_over = true
                }
                const select_id_max = game.PK.selects().length -1
                aiwrap.ask_select_a(ai_pflag, game.PK.selects(), (text)=>{
                    const id = Number(text)
                    if (id < 0){return error()}
                    if (id > select_id_max){return error()}
                    Data.ai_in_think = false
                    Event.on_move(game.PK.selects()[id])
                })
            // 正常走棋
            }else{move()}
        }
        if (! (Data.has_over || Data.ai_in_think)){
            Data.ai_in_think = true
            begin_fuck()
        }
    },
    on_take_ai_msg(msg){
        const code = aiport.pop(aiport.decode(msg))
        if ("ERROR" == code.id){
            console.log("AI-Error:", code.args[0])
        }else if ("SAY" == code.id){
            Data.ai_say = code.args[0]
        }
    },
    on_not_game_box(){
        if (game.PK.timer_has_start() && (! Data.ai_in_think)){
            game.PK.timer_stop()
        }
    },
    on_start(){
        if (Data.ai_in_think){aiwrap.restart()}
        const use_ai = [...(Data.use_ai)]
        Data.use_ai[0] = false
        Data.use_ai[1] = false
        Data.reset()
        GameRecord.clear()
        game.PK.reset()
            .set_timelimit(Data.maxtime_total, Data.maxtime_step).timer_start();
        aiwrap.reset()
            .set_board(game.PK.board())
            .set_rule_switch("forbidden", game.Rules[Data.rule].use_forbidden)
            .set_args(...(Data.ai_last_args))
        Data.use_ai[0] = use_ai[0]
        Data.use_ai[1] = use_ai[1]
    },
    on_init(aiport, kvs){
        Data.init(kvs)
        aiport.listen(Event.on_take_ai_msg)
        aiwrap.send = aiport.send
        aiwrap.restart = aiport.restart
    },
}
Ux.frame_add("EVENT", ()=>{
    return ()=>{
        if (Data.has_over){
            if (game.PK.timer_has_start()){game.PK.timer_stop()}
            return
        }
        const time_step = game.PK.timer_step(game.PK.next_pflag())
        if (time_step < 1){
            Data.has_over = true
            Data.winner_pflag = game.P_swap(game.PK.next_pflag())
            Data.lose_note = `超时`
            game.PK.timer_stop()
            return
        }

        if ((Data.select_nums < 0) && (game.Rules[Data.rule].select_nums(game.PK.next_step()) > 0)){
            Data.select_nums = game.Rules[Data.rule].select_nums(game.PK.next_step())
        }
        if ((0 == Data.swap_nums) && game.Rules[Data.rule].can_swap(game.PK.next_step())){
            Data.swap_nums = 1
        }

        if (game.PK.timer_has_start()){
            const player = Data.pflag_to_player(game.PK.next_pflag())
            if (0 == Data.select_nums){
                if ((Data.use_ai[0] && Data.use_ai[1]) || ((Data.use_ai[0] ^ Data.use_ai[1]) && (Data.use_ai[player^1]))){
                    Event.on_ai_step()
                }
            }else if (Data.use_ai[player]){
                Event.on_ai_step()
            }
        }
    }
})

Ux.frame_add("top", ()=>{
    const dom = $("#top-title")
    Ux.state_watch("top.title", (val)=>{
        dom.html(val)
    })
    return ()=>{
        const cur_step = GameRecord.step_nums()
        let text_time_step = `${game.PK.timer_step(game.PK.next_pflag())}秒`
        if (! game.PK.timer_has_start()){
            text_time_step = `${text_time_step}(暂停)`
        }
        if (cur_step > 0){
            const cur_pflag_name = game.P_name(game.PK.next_pflag())
            if (Data.has_over){
                let over_info = "和棋"
                if (Data.winner_pflag != game.P_EMPTY){
                    over_info = ` ${game.P_name(Data.winner_pflag)}胜`
                    if (Data.lose_note.length > 0){
                        over_info = `${game.P_name(game.P_swap(Data.winner_pflag))}${Data.lose_note} ${over_info}`
                    }
                }
                Ux.state["top.title"] = `共${cur_step}手 ${over_info}`
            }else{
                Ux.state["top.title"] = `第${cur_step+1}手 ${cur_pflag_name} ${text_time_step}`
            }
        }else if(Data.has_over){
            Ux.state["top.title"] = `先手 ${game.P_name(game.PK.next_pflag())} ${Data.lose_note} (${game.P_name(Data.winner_pflag)}胜)`
        }else{
            Ux.state["top.title"] = `先手 ${game.P_name(game.PK.next_pflag())} ${text_time_step}`
        }
    }
})

Ux.frame_add(["box", "newgame"], ()=>{
    const dom = $("#box-newgame")

    direct2d.Ratio(dom.find(`input[name="newgame-in-rule"]`), {
        on_select(val){
            Data.kvs.text("rule", val)
        },
    }).select_with_eq(Data.kvs.text("rule"))

    const dom_use_ai = direct2d.Select(dom.find(`input[name="newgame-in-use-ai"]`), {
        on_select(val){
            Data.use_ai[Number(val)] = true
        },
        on_unselect(val){
            Data.use_ai[Number(val)] = false
        },
    })

    function limit_maxtime(target, a, fcomp_ab){
        target.foreach((val, session)=>{
            const b = Number(val)
            session.show(fcomp_ab(a, b))
        })
    }
    const dom_maxtimes = [direct2d.Option(dom.find("#newgame-in-maxtime-total"), {
        on_select(val){
            console.log("select-total", val)
            const a = Number(val)
            limit_maxtime(dom_maxtimes[1], a, R.gt)
            Data.kvs.number("maxtime-total", a)
        }
    }), direct2d.Option(dom.find("#newgame-in-maxtime-step"), {
        on_select(val){
            console.log("select-step", val)
            const a = Number(val)
            Data.kvs.number("maxtime-step", a)
        }
    })]
    function init_maxtime(){
        const maxtimes = [Data.kvs.text("maxtime-total"), Data.kvs.text("maxtime-step")]
        dom_maxtimes[0].select_with_eq(maxtimes[0])
        dom_maxtimes[1].select_with_eq(maxtimes[1])
        limit_maxtime(dom_maxtimes[1], Number(maxtimes[0]), R.gt)
    }

    function frame(){
        Event.on_not_game_box()
        dom_use_ai.select((val)=>(Data.use_ai[Number(val)]))
    }

    dom.find("#newgame-cmd-start").click(function(){
        Event.on_start()
        dom_use_ai.foreach((val, checked)=>{
            Data.use_ai[Number(val)] = checked
        })
        Ux.box_to("game")
    })
    init_maxtime()
    return frame
})

Ux.frame_add(["box", "game"], ()=>{
    const dom_parent = $("#box-game")
    const dom = $("#game-chessboard")
    const d2d = direct2d.for_canvas(dom.get(0))
    const ctx = {
        // 棋盘画布元素相对页面的位置
        board_rect:undefined,
        // 棋盘边长(px)
        board_len:0,
        // 棋盘格子间距(px)
        pad:0,
        pad_mid:0,
    }

    function loc_to_pos(loc){
        const x = (game.to_x(loc) + 1) * ctx.pad
        const y = (game.to_y(loc) + 1) * ctx.pad
        return [x,y]
    }
    function pos_to_loc(x, y){
        const loc_x = Math.min(game.BOARD_SIZE, Math.max(Math.floor(x / ctx.pad), 1)) - 1
        const loc_y = Math.min(game.BOARD_SIZE, Math.max(Math.floor(y / ctx.pad), 1)) - 1
        return game.to_i(loc_x, loc_y)
    }

    // 第一次点击某个点为选择，第二次点击此点为走棋
    function on_tap(pos_x, pos_y){
        const loc = pos_to_loc(pos_x, pos_y)
        if (Data.select == loc){
            if (! Data.has_over){Event.on_move(loc)}
        }else{
            Data.select = loc
        }
    }

    function draw_chessboard(draw){
        const line_end = ctx.board_len - ctx.pad
        // 纵横线
        draw.set_outline_color(Consts.BOARD_LINE_COLOR)
            .set_outline_width(1).path_begin()
        for (let i = 1; i < game.BOARD_SIZE +1; i++){
            const pos = i * ctx.pad
            draw.hline(ctx.pad, line_end, pos).vline(ctx.pad, line_end, pos)
        }
        draw.path_outline()
        // 坐标
        const label_size = ctx.pad_mid
        draw.set_fill_color(Consts.BOARD_LABEL_COLOR)
            .set_font(label_size, Consts.BOARD_LABEL_FONT)
            .set_text_align("center").set_text_baseline("middle")
        for (let i = 1; i < game.BOARD_SIZE +1; i++){
            const pos = i * ctx.pad
            const hlabel = String.fromCodePoint(('A'.codePointAt(0))-1+i)
            const vlabel = (game.BOARD_SIZE+1-i).toString()
            draw.text(hlabel, pos, ctx.pad_mid)
                .text(hlabel, pos, ctx.board_len-ctx.pad_mid)
                .text(vlabel, ctx.pad_mid, pos)
                .text(vlabel, ctx.board_len-ctx.pad_mid, pos)
        }
        // 星位
        draw.set_fill_color(Consts.BOARD_STAR_COLOR)
        const star_size = Math.floor(Consts.BOARD_STAR_SIZE_RATE * ctx.pad)
        game.STARS.forEach((_)=>{
            const pos = loc_to_pos(game.to_i(_[0], _[1]))
            draw.path_begin().circular(pos[0], pos[1], star_size).path_fill()
        })
    }
    function draw_chesspiece(draw){
        const size = ctx.pad_mid-1
        function on_piece(loc, color){
            const [x,y] = loc_to_pos(loc)
            draw.set_fill_color(color).path_begin().circular(x, y, size).path_fill()
        }
        draw.push()
        game.PK.for_pieces((pflag, loc)=>{
            if (pflag == game.P_BLACK){
                on_piece(loc, Consts.PIECE_BLACK_COLOR)
            } else if (pflag == game.P_WHITE){
                on_piece(loc, Consts.PIECE_WHITE_COLOR)
            }
        })
        function on_step(i, loc, is_select=false){
            const [x,y] = loc_to_pos(loc)
            if (is_select){
                draw.set_fill_color(Consts.PIECE_SELECT_COLOR)
            }else{
                draw.set_fill_color(Consts.PIECE_STEP_COLOR[i%2])
            }
            draw.text(i.toString(), x, y)
        }
        draw.set_font(size+2, Consts.PIECE_STEP_FONT, "bold")
            .set_text_align("center").set_text_baseline("middle")
        // 显示棋子步数
        GameRecord.foreach((step, m)=>{on_step(step, m.loc)})
        // 显示打点位步数
        game.PK.selects().forEach((loc)=>{on_step(game.PK.next_step(), loc, true)})
        draw.pop()
    }
    function draw_select(draw){
        if (Data.select >= 0){
            const pos = loc_to_pos(Data.select)
            const x = pos[0] - ctx.pad_mid
            const y = pos[1] - ctx.pad_mid
            const w = x + ctx.pad
            const h = y + ctx.pad
            const line_size = ctx.pad_mid/2
            draw.push().set_outline_width(2).set_outline_color(Consts.BOARD_SELECT_COLOR)
                .path_begin()
                .hline(x, x+line_size, y).vline(y, y+line_size, x)
                .hline(w-line_size, w, y).vline(y, y+line_size, w)
                .hline(x, x+line_size, h).vline(h-line_size, h, x)
                .hline(w-line_size, w, h).vline(h-line_size, h, w)
                .path_outline().pop()
        }
    }
    function draw_forbidden_moves(draw){
        if (! Data.has_over){
            draw.push().set_outline_color(Consts.FORBIDDEN_MOVE_COLOR).set_outline_width(4)
            Data.forbidden_moves.forEach((loc)=>{
                const [x,y] = loc_to_pos(loc)
                const radius = Math.max(ctx.pad_mid-2, 6)
                draw.path_begin()
                    .line(x+radius, y+radius, x-radius, y-radius)
                    .line(x+radius, y-radius, x-radius, y+radius)
                    .path_outline()
            })
            draw.pop()
        }
    }

    const dom_infos = {
        rule:$("#game-info-rule"),
        maxtime:$("#game-info-maxtime"),
        timer_black:$("#game-info-timer-black"),
        timer_white:$("#game-info-timer-white"),
        status:$("#game-info-status"),
    }
    function init_infos(){
        Ux.state_watch("game.info.rule", (val)=>{
            dom_infos.rule.html(val)
        })
        Ux.state_watch("game.info.maxtime", (val)=>{
            dom_infos.maxtime.html(val)
        })
        Ux.state_watch("game.info.timer-black", (val)=>{
            dom_infos.timer_black.html(val)
        })
        Ux.state_watch("game.info.timer-white", (val)=>{
            dom_infos.timer_white.html(val)
        })
        Ux.state_watch("game.info.status", (val)=>{
            dom_infos.status.html(val)
        })
    }
    function frame_infos(){
        function format_time(seconds){
            const over_second = seconds % 60
            const text_second = (over_second < 10) ? `0${over_second}` : over_second.toString()
            const m = Math.floor(seconds / 60)
            if (m < 1){return `00:${text_second}`}
            if (m < 10){return `0${m}:${text_second}`}
            return `${m}:${text_second}`
        }
        if (R.is(String, Data.rule)){Ux.state["game.info.rule"] = Data.rule}
        Ux.state["game.info.maxtime"] = `${format_time(Data.kvs.text("maxtime-step"))}/${format_time(Data.kvs.text("maxtime-total"))}`
        Ux.state["game.info.timer-black"] = `黑${format_time(game.PK.timer_step(game.P_BLACK))}/${format_time(game.PK.timer_total(game.P_BLACK))}`
        Ux.state["game.info.timer-white"] = `白${format_time(game.PK.timer_step(game.P_WHITE))}/${format_time(game.PK.timer_total(game.P_WHITE))}`
        if (game.PK.next_pflag() == game.P_BLACK){
            dom_infos.timer_white.removeClass("cur-player")
            dom_infos.timer_black.addClass("cur-player")
        }else{
            dom_infos.timer_black.removeClass("cur-player")
            dom_infos.timer_white.addClass("cur-player")
        }
    }

    const dom_tools = {
        use_ai_black:$("#game-use-ai-black"),
        use_ai_white:$("#game-use-ai-white"),
        cmd_prev:$("#game-cmd-prev"),
        cmd_next:$("#game-cmd-next"),
        cmd_load_first:$("#game-cmd-load-first"),
        cmd_load_last:$("#game-cmd-load-last"),
        cmd_restart:$("#game-cmd-restart"),
        cmd_giveup:$("#game-cmd-giveup"),
        cmd_swap:$("#game-cmd-swap"),
    }
    function init_tools(){
        dom_tools.use_ai_black.bind("click", function(){
            if (! Data.ai_in_think){
                Data.use_ai[0] = (! Data.use_ai[0])
            }
        })
        dom_tools.use_ai_white.bind("click", function(){
            if (! Data.ai_in_think){
                Data.use_ai[1] = (! Data.use_ai[1])
            }
        })
        dom_tools.cmd_prev.bind("click", Event.on_prev)
        dom_tools.cmd_next.bind("click", Event.on_next)
        dom_tools.cmd_load_first.bind("click", Event.on_load_first)
        dom_tools.cmd_load_last.bind("click", Event.on_load_last)
        dom_tools.cmd_restart.bind("click", ()=>{
            console.clear()
            Event.on_start()
        })
        dom_tools.cmd_giveup.bind("click", Event.on_giveup)
        dom_tools.cmd_swap.bind("click", Event.on_swap)
    }
    function frame_tools(){
        const step_nums = GameRecord.step_nums()
        const step_wait = game.PK.next_step()
        if (Data.has_over){
            if (Data.winner_pflag == game.P_EMPTY){
                Ux.state["game.info.status"] = "和棋"
            }else{
                const winner_name = game.P_name(Data.winner_pflag)
                const lose_name = game.P_name(game.P_swap(Data.winner_pflag))
                const lose_note = (Data.lose_note.length > 0) ? `${lose_name} ${Data.lose_note} ` : `${GameRecord.at(step_nums-1).note} `
                Ux.state["game.info.status"] = `${lose_note}${winner_name}胜`
            }
            dom_tools.cmd_giveup.removeClass("button")
        }else if (Data.ai_in_think){
            if (Data.ai_say.length > 0){
                Ux.state["game.info.status"] = `AI: ${Data.ai_say}`
            }else{
                Ux.state["game.info.status"] = `AI正在思考`
            }
            dom_tools.cmd_giveup.removeClass("button")
            dom_tools.cmd_swap.removeClass("button")
        }else{
            if (step_nums < 1){
                Ux.state["game.info.status"] = `等待 第1手棋`
            }else{
                const last_step = step_nums -1
                const log = GameRecord.at(last_step)
                const ex = (((1 == Data.swap_nums) && (Data.use_ai[0] ^ Data.use_ai[1])) ? " 可交换" : "")
                if (Data.select_nums >= 0){
                    const note = (Data.select_nums > 0) ? `请给出打点(还剩${Data.select_nums}个)` : "请双击要保留的打点"
                    Ux.state["game.info.status"] = `第${step_wait}手 ${note}`
                }else if(2 == Data.swap_nums){
                    Ux.state["game.info.status"] = `第${step_nums}手 ${game.P_name(log.pflag)}(${game.text_i(log.loc)}) 已交换`
                }else{
                    Ux.state["game.info.status"] = `第${step_nums}手 ${game.P_name(log.pflag)}(${game.text_i(log.loc)}) ${log.note}${ex}`
                }
            }

            if (Data.select_nums > 0){
                dom_tools.cmd_giveup.removeClass("button")
            }else{
                dom_tools.cmd_giveup.addClass("button")
            }
        }
        if ((1 == Data.swap_nums) && (Data.use_ai[0] ^ Data.use_ai[1])){
            dom_tools.cmd_swap.addClass("button")
        }else{
            direct2d.update_html(dom_tools.cmd_swap, dom_tools.cmd_swap.attr((2 == Data.swap_nums) ? "ux-a" : "ux-b"))
            dom_tools.cmd_swap.removeClass("button")
        }
        direct2d.update_prop(dom_tools.use_ai_black, "checked", Data.use_ai[0])
        direct2d.update_prop(dom_tools.use_ai_white, "checked", Data.use_ai[1])
    }

    function init_page(){
        dom.bind("click", function(e){
            const x = Math.round(e.clientX - ctx.board_rect.left) + ctx.pad_mid
            const y = Math.round(e.clientY - ctx.board_rect.top) + ctx.pad_mid
            on_tap(x, y)
        })
        init_infos()
        init_tools()
    }
    function frame_page(){
        if (! game.PK.timer_has_start()){
            game.PK.timer_start()
        }
        ctx.board_rect = dom.get(0).getBoundingClientRect()
        ctx.board_len = Math.min(dom_parent.width(), dom_parent.height()) * Consts.BOARD_LEN_RATE
        ctx.pad = ctx.board_len / (game.BOARD_SIZE +1)
        ctx.pad_mid = ctx.pad / 2
        d2d.resize(ctx.board_len, ctx.board_len)
        frame_infos()
        frame_tools()
    }

    d2d.on_before = (draw)=>{
        draw.clear_all().set_opacity(1.0)
        R.forEach(R.applyTo(draw), [
            draw_chessboard,
            draw_chesspiece,
            draw_select,
            draw_forbidden_moves,
        ])
    }
    init_page()
    frame_page()
    utils.clock.sync_refresh(d2d.frame)
    return frame_page
})

Ux.frame_add(["box", "about"], ()=>{
    return Event.on_not_game_box
})

Ux.frame_add(["box", "setting"], ()=>{
    function limit_depth(target, max){
        target.foreach((val, session)=>{
            session.show(Number(val) <= max)
        })
    }
    const max_depth_vc = direct2d.Option($("#set-vc-max-depth"), {
        on_select(val){
            Data.kvs.text("ai.vc-max-depth", val)
        }
    }).select_with_eq(Data.kvs.text("ai.vc-max-depth"))

    direct2d.Option($("#set-uct-max-time"), {
        on_select(val){
            Data.kvs.text("ai.uct-max-time", val)
            limit_depth(max_depth_vc, Number(val))
        }
    }).select_with_eq(Data.kvs.text("ai.uct-max-time"))

    direct2d.Option($("#set-search-depth"), {
        on_select(val){
            Data.kvs.text("ai.search-depth", val)
        }
    }).select_with_eq(Data.kvs.text("ai.search-depth"))

    return Event.on_not_game_box
})

Ux.frame_add("tabs", ()=>{
    return ()=>{}
})

function init(aiport, kvs){
    Ux.state = new Proxy(Ux._state, {
        get(me, name){
            return me[name]
        },
        set(me, name, val){
            if (me[name] != val){
                Reflect.set(me, name, val, me)
                const watchs = Ux._state_watchs[name]
                if (watchs instanceof Array){
                    watchs.forEach(R.applyTo(val))
                }
            }
            return val
        },
    })
    Event.on_init(aiport, kvs)
    // 移动端的初始页面宽度必定小于高度不少
    if ("pc" == direct2d.adaption_mobile_width(Consts.PC_PAGEWIDTH_RATE)){
        // 移动端的cursor会妨碍导致棋盘闪烁
        // 所以仅PC端才启用棋盘的鼠标样式
        $("#game-chessboard").css("cursor", "pointer")
    }
    $("#box").children("div").each(function(){Ux.box_add($(this))})
    Ux.box_to("game")
    Ux.frame_add_done()
    $(document.body).show()
    Event.on_start()
    window.step = Event.on_ai_step
}

export default {
    init,
    frame:Ux.frame,
}
